#ifndef GAMEOBJECT_H
#define GAMEOBJECT_H

#include "scene.h"
#include <csugl.h>
#include <type_traits>
#include <typeindex>
#include <entt/entt.hpp>
#include <iostream>

#include <glog/logging.h>

struct Component;
struct System;

struct GameObject
{
    friend Component;
    friend System;

    GameObject(Scene* scene)
        : _scene(scene)
    {
        _obj_id = _scene->Regi().create();
    }

    uint32_t GetID() { return static_cast<uint32_t>(_obj_id); }

    bool operator==(const GameObject &obj)
    {
        return obj._obj_id == this->_obj_id;
    }

    bool operator==(GameObject &&obj)
    {
        return obj._obj_id == this->_obj_id;
    }

    template <typename _CT>
    bool HasComponent()
    {
        return _scene->Regi().has<_CT>(_obj_id);
    }

    template <typename _CT, typename... Args>
    void AddComponent(Args &&...args)
    {
        auto& regi = _scene->Regi();
        bool component_check = std::is_base_of_v<Component, _CT>;
        auto index = std::type_index(typeid(_CT));
        CHECK(component_check) << ": '" << index.name() << "' is not a Component";
        bool not_had_component = !HasComponent<_CT>();
        CHECK(not_had_component) << ": '" << index.name() << "' already exist";
        regi.emplace<_CT>(_obj_id, std::forward<Args>(args)...);
        regi.view<_CT>().template get<_CT>(_obj_id)._obj = this;
    }

    /**
     * Use {in-situ} consturctor or std::move
     * @param comp: new component
     */
    template <typename _CT>
    void AddComponent(_CT &&comp)
    {
        bool component_check = std::is_base_of_v<Component, _CT>;
        auto index = std::type_index(typeid(_CT));
        CHECK(component_check) << ": '" << index.name() << "' is not a Component";
        bool not_had_component = !HasComponent<_CT>();
        CHECK(not_had_component) << ": '" << index.name() << "' is already exist";
        comp._obj = this;
        _scene->Regi().emplace<_CT>(_obj_id, comp);
    }

    template <typename _CT>
    _CT *GetComponent()
    {
        return HasComponent<_CT>() ? &_scene->Regi().view<_CT>().template get<_CT>(_obj_id) : nullptr;
    }

    // TODO
    // std::tuple<>* GetComponents()
    // {
    //     auto view = &_scene->Regi().view(_obj_id);
    // }

private:
    entt::entity _obj_id;
    Scene* _scene;
};

#endif